package be.rivendale.mathematics;

import org.junit.Test;

import static be.rivendale.mathematics.MathematicalAssert.*;
import static org.junit.Assert.*;

public class VectorTest {
    @Test
    public void constructorByCoordinatesSetsCoordinates() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        assertRealEquals(1, vector.getX());
        assertRealEquals(2, vector.getY());
        assertRealEquals(3, vector.getZ());
    }

    @Test
    public void normalizeReturnsUnitVectorOfThisVector() {
        Vector normalizedVector = new Triple(1, 2, 3).normalize();
        assertRealEquals(1, normalizedVector.length());
    }

    @Test
    public void normalizeLeavesStateOfThisVectorUnchanged() {
        Vector vector = new Triple(1, 2, 3);
        vector.normalize();
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void normalizeThrowsIllegalArgumentExceptionWhenLengthIsZero() throws Exception {
        try {
            Triple.ZERO_VECTOR.normalize();
            failExceptionExpected();
        } catch(IllegalArgumentException expected) {
            assertEquals("A zero vector can not be normalized", expected.getMessage());
        }
    }

    @Test
    public void lengthReturnsMagnitudeOfVector() throws Exception {
        assertRealEquals(1, new Triple(1, 0, 0).length());
        assertRealEquals(0, new Triple(0, 0, 0).length());
        assertRealEquals(3.741657, new Triple(1, 2, 3).length());
    }

    @Test
    public void crossProductLeavesStateOfThisVectorUnchanged() {
        Vector vector = new Triple(1, 2, 3);
        vector.crossProduct(new Triple(0, 0, 0));
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void crossProductReturnsCrossProductOfTwoVectors() throws Exception {
        Vector expectedVector = new Triple(-3, 6, -3);
        Vector a = new Triple(1, 2, 3);
        Vector b = new Triple(4, 5, 6);
        Vector crossProduct = a.crossProduct(b);
        assertVectorEquals(expectedVector, crossProduct);
    }

    @Test
    public void crossProductLeavesStateOfParameterVectorUnchanged() throws Exception {
        Vector factor = new Triple(4, 5, 6);
        new Triple(1, 2, 3).crossProduct(factor);
        assertVectorEquals(new Triple(4, 5, 6), factor);
    }

    @Test
    public void crossProductThrowsIllegalArgumentExceptionWhenVectorParameterIsNull() throws Exception {
        try {
            new Triple(0, 0, 0).crossProduct(null);
            failExceptionExpected();
        } catch(IllegalArgumentException expectedException) {
            assertEquals("The second vector may not be null to calculate the cross product.", expectedException.getMessage());
        }
    }

    @Test
    public void dotProductLeavesThisVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        vector.dotProduct(new Triple(4, 5, 6));
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void dotProductLeavesParameterVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        new Triple(4, 5, 6).dotProduct(vector);
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void dotProductThrowsIllegalArgumentExceptionWhenParameterVectorIsNull() throws Exception {
        try {
            new Triple(1, 2, 3).dotProduct(null);
            failExceptionExpected();
        } catch(IllegalArgumentException expectedException) {
            assertEquals("The second vector may not be null to calculate the dot product.", expectedException.getMessage());
        }
    }

    @Test
    public void dotProductReturnsDotProductOfBothVectors() throws Exception {
        Vector a = new Triple(1, 3, -5);
        Vector b = new Triple(4, -2, -1);
        double dotProduct = a.dotProduct(b);
        assertRealEquals(3, dotProduct);
    }

    @Test
    public void angleThrowsIllegalArgumentExceptionWhenThisVectorIsAZeroVector() throws Exception {
        try {
            Triple.ZERO_VECTOR.angle(new Triple(1, 2, 3));
            failExceptionExpected();
        } catch(IllegalArgumentException expectedExeption) {
            assertEquals("Neither of the two vectors may be zero vectors to calculate the angle between them", expectedExeption.getMessage());
        }
    }

    @Test
    public void angleThrowsIllegalArgumentExceptionWhenTheSpecifiedVectorIsNull() throws Exception {
        try {
            new Triple(1, 2, 3).angle(null);
            failExceptionExpected();
        } catch(IllegalArgumentException expectedExeption) {
            assertEquals("Angle between two vectors can not be determined if the second vector is null", expectedExeption.getMessage());
        }
    }

    @Test
    public void angleThrowsIllegalArgumentExceptionWhenTheSpecifiedVectorIsAZeroVector() throws Exception {
        try {
            new Triple(1, 2, 3).angle(Triple.ZERO_VECTOR);
            failExceptionExpected();
        } catch(IllegalArgumentException expectedExeption) {
            assertEquals("Neither of the two vectors may be zero vectors to calculate the angle between them", expectedExeption.getMessage());
        }
    }

    @Test
    public void angleLeavesStateOfThisVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        vector.angle(new Triple(4, 5, 6));
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void angleLeavesStateOfParameterVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        new Triple(4, 5, 6).angle(vector);
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void angleReturnsAngleBetweenTheTwoInvolvedVectors() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        double angle = vector.angle(new Triple(4, 5, 6));
        assertRealEquals(0.225726, angle);
    }

    @Test
    public void isZeroVectorReturnsTrueIfVectorEqualsZeroVector() throws Exception {
        assertTrue(new Triple(0, 0, 0).isZeroVector());
    }

    @Test
    public void isZeroVectorReturnsFalseIfVectorDoesNotEqualZeroVector() throws Exception {
        assertFalse(new Triple(1, 2, 3).isZeroVector());
    }

    @Test
    public void isNormalizedReturnsTrueIfVectorIsNormalized() throws Exception {
        assertTrue(new Triple(1, 2, 3).normalize().isNormalized());
    }

    @Test
    public void isNormalizedReturnsFalseIfVectorIsNotNormalized() throws Exception {
        assertFalse(new Triple(1, 2, 3).isNormalized());
    }

    @Test
    public void multiplyReturnsMultiplicationOfScalarValueWithVector() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        Vector productVector = vector.multiply(2);
        assertVectorEquals(new Triple(2, 4, 6), productVector);
    }

    @Test
    public void multiplyLeavesStateOfVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        vector.multiply(2);
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

        @Test
    public void equalsReturnsTrueIfTheTwoVectorsAreEqual() throws Exception {
        Vector a = new Triple(1, 2, 3);
        Vector b = new Triple(1, 2, 3);
        assertTrue(a.equals(b));
    }

    @Test
    public void equalsReturnsFalseOfTheTwoVectorsAreNotEqual() throws Exception {
        Vector a = new Triple(1, 2, 3);
        Vector b = new Triple(4, 5, 6);
        assertFalse(a.equals(b));
    }

    @Test(expected = IllegalArgumentException.class)
    public void isParallelToThrowsIllegalArgumentExceptionWhenTheSpecifiedVectorIsNull() throws Exception {
        new Triple(1, 2, 3).isParallelTo(null);
    }

    @Test
    public void isParallelToReturnsTrueIfTheAngleBetweenTheTwoRaysIsZero() throws Exception {
        assertTrue(new Triple(1, 2, 3).isParallelTo(new Triple(0.25, 0.5, 0.75)));
    }

    @Test
    public void isParalllelToReturnsFalseIfTheAngleBetweenTheTwoIsNotZero() throws Exception {
        assertFalse(new Triple(1, 2, 3).isParallelTo(new Triple(5, 8, 12)));
    }

    @Test(expected = IllegalArgumentException.class)
    public void isPerpendicularToThrowsIllegalArgumentExceptionWhenSpecifiedVectorIsNull() throws Exception {
        new Triple(1, 2, 3).isPerpendicularTo(null);
    }

    @Test
    public void isPerpendicularToReturnsTrueIfBothVectorsArePerpendicularToEachother() throws Exception {
        assertTrue(new Triple(1, 0, 0).isPerpendicularTo(new Triple(0, 1, 1)));
    }

    @Test
    public void isPerpendicularToReturnsFalseIfBothVectorsAreNotPerpendicularToEachother() throws Exception {
        assertFalse(new Triple(1, 2, 3).isPerpendicularTo(new Triple(8, 6, 5)));
    }

    @Test
    public void divideLeavesStateOfThisVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        vector.divide(2);
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test
    public void divideReturnsVectorWithEachCoordinateDividedByTheScalarSpecified() throws Exception {
        assertVectorEquals(new Triple(0.5, 1, 1.5), new Triple(1, 2, 3).divide(2));
    }

    @Test
    public void addLeavesStateOfThisVectorUnchanged() throws Exception {
        Vector vector = new Triple(1, 2, 3);
        vector.add(new Triple(4, 5, 6));
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }

    @Test(expected = IllegalArgumentException.class)
    public void addThrowsIllegalArgumentExceptionWhenSpecifiedPointIsNull() throws Exception {
        new Triple(1, 2, 3).add((Vector) null);
    }

    @Test
    public void addReturnsVectorThatHasAllCoordinatesAsTheSomOfTheCoordinatesFromThisVectorAndTheSpecifiedVector() throws Exception {
        Vector sum = new Triple(1, 2, 3).add((Vector) new Triple(4, 5, 6));
        assertVectorEquals(new Triple(5, 7, 9), sum);
    }

    @Test(expected = IllegalArgumentException.class)
    public void scalarTripleProductThrowsIllegalArgumentExceptionWhenVectorBIsNull() throws Exception {
        new Triple(1, 2, 3).scalarTripleProduct(null, new Triple(4, 5, 6));
    }

    @Test(expected = IllegalArgumentException.class)
    public void scalarTripleProductThrowsIllegalArgumentExceptionWhenVectorAIsNull() throws Exception {
        new Triple(1, 2, 3).scalarTripleProduct(new Triple(4, 5, 6), null);
    }

    @Test
    public void scalarTripleProductReturnsTheDotProductWithTheThirdVectorOfTheCrossProductOfTwoVectors() throws Exception {
        Vector a = new Triple(-1, 2, 3);
        Vector b = new Triple(8, 7, -9);
        Vector c = new Triple(10, -5, 3);
        assertRealEquals(a.crossProduct(b).dotProduct(c), a.scalarTripleProduct(b, c));
    }

    @Test
    public void scalarTripleProductIsCommutative() throws Exception {
        Vector a = new Triple(15, 8, 7);
        Vector b = new Triple(-6, 8, -8);
        Vector c = new Triple(7, -5, -4);
        assertRealEquals(a.scalarTripleProduct(b, c), a.scalarTripleProduct(b, c));
    }

    @Test
    public void asPointReturnsThisVectorAsAPoint() {
        assertPointEquals(new Triple(1, 2, 3), new Triple(1, 2, 3).asPoint());
    }

    @Test
    public void invertReturnsVectorWithAllCoordinatesInverted() {
        Vector vector = new Triple(1, 2, 3);
        assertVectorEquals(new Triple(-1, -2, -3), vector.invert());
    }

    @Test
    public void invertLeavesStateOfThisVectorUnchanged() {
        Vector vector = new Triple(1, 2, 3);
        vector.invert();
        assertVectorEquals(new Triple(1, 2, 3), vector);
    }
	
	@Test
	public void projectLeavesThisVectorUnchanged() {
	    Vector vector = new Triple(1, 2, 3);
		vector.project(new Triple(5, 6, 7).normalize());
		assertVectorEquals(new Triple(1, 2, 3), vector);
	}
	
	@Test
	public void projectLeavesTheVectorOnWhichToProjectUnchanged() {
		Vector vector = new Triple(5, 6, 7).normalize();
		new Triple(1, 2, 3).project(vector);
		assertVectorEquals(new Triple(5, 6, 7).normalize(), vector);
	}
	
	@Test
	public void projectResultsInAVectorParallelToTheVectorOnWhichToProject() {
		Vector a = new Triple(1, 2, 3);
		Vector b = new Triple(0.455842, 0.569803, 0.683763);
		Vector c = a.project(b);
		assertTrue(c.isParallelTo(b));

	}
	
	@Test
	public void projectResultsInTheCorrectlyProjectedVector() {
		Vector vectorToProject = new Triple(1, 1, 1);
		Vector vectorOnWhichToProject = new Triple(1, 2, 3).normalize();
		Vector projectedVector = vectorToProject.project(vectorOnWhichToProject);
		assertVectorEquals(new Triple(0.428571, 0.857143, 1.285714), projectedVector);
	}

	@Test(expected = IllegalArgumentException.class)
	public void projectRequiresVectorToProjectOn() {
	    new Triple(1, 2, 3).project(null);
	}

	@Test(expected = IllegalArgumentException.class)
	public void projectRequiresANormalizedVector() {
		Triple unnormalizedVector = new Triple(1, 2, 3);
		new Triple(1, 2, 3).project(unnormalizedVector);
	}
}
